import { BaseAgent, BaseAgentConfig, AgentCommandConfig } from "./base";
import { ModelConfig } from "./utils";
import {
  AgentType,
  ClaudeConfig,
  ClaudeResponse,
  ClaudeStreamCallbacks,
  Conversation,
  ModelProvider,
} from "../types";

export class ClaudeAgent extends BaseAgent {
  private anthropicApiKey?: string;
  private oauthToken?: string;
  private model?: string;
  private useOAuth: boolean;
  private tokenInitialized: boolean = false;

  private escapePrompt(prompt: string): string {
    // Comprehensive escaping for bash double quotes - production ready
    return prompt
      .replace(/\\/g, '\\\\')    // Escape backslashes FIRST
      .replace(/"/g, '\\"')      // Escape double quotes
      .replace(/'/g, "'\\''")    // Escape single quotes (close quote, escaped quote, reopen)
      .replace(/\$/g, '\\$')     // Escape dollar signs
      .replace(/`/g, '\\`')      // Escape backticks (command substitution)
      .replace(/!/g, '\\!')      // Escape exclamation (history expansion)
      .replace(/\n/g, '\\n')     // Escape newlines
      .replace(/\r/g, '\\r')     // Escape carriage returns
      .replace(/\t/g, '\\t');    // Escape tabs
  }

  constructor(config: ClaudeConfig) {
    const baseConfig: BaseAgentConfig = {
      sandboxProvider: config.sandboxProvider,
      secrets: config.secrets,
      sandboxId: config.sandboxId,
      workingDirectory: config.workingDirectory,
    };

    super(baseConfig);

    // Validate that provider is anthropic if specified (Claude only supports anthropic)
    if (config.provider && config.provider !== "anthropic") {
      throw new Error("Claude agent only supports 'anthropic' provider");
    }

    // Store config values
    const envOAuthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
    this.oauthToken = config.oauthToken || envOAuthToken;
    this.anthropicApiKey = config.providerApiKey;
    this.model = config.model;
    
    // Perform a preliminary check to set useOAuth based on the presence of oauthToken and absence of anthropicApiKey.
    // The final determination of the authentication method is made in initializeToken().
    this.useOAuth = !!(this.oauthToken && !this.anthropicApiKey);
  }
  
  private async initializeToken(): Promise<void> {
    if (this.tokenInitialized) return;
    
    // No automatic OAuth token loading - users must provide tokens themselves
    
    // Determine which auth method to use
    this._determineAuthMethod();
  }

  /**
   * Determines the authentication method to use (OAuth or API key).
   * Sets the `useOAuth` flag based on the available credentials.
   */
  private _determineAuthMethod(): void {
    if (this.oauthToken) {
      this.useOAuth = true;
    } else if (this.anthropicApiKey) {
      this.useOAuth = false;
    } else {
      throw new Error(
        "Claude agent requires either providerApiKey or oauthToken. Run 'vibekit auth claude' to authenticate."
      );
    }
    
    // Validate that at least one auth method is provided
    if (!this.anthropicApiKey && !this.oauthToken) {
      throw new Error(
        "Claude agent requires either providerApiKey or oauthToken. Run 'vibekit auth claude' to authenticate."
      );
    }
    
    this.tokenInitialized = true;
  }

  protected getCommandConfig(
    prompt: string,
    mode?: "ask" | "code"
  ): AgentCommandConfig {
    let instruction: string;
    if (mode === "ask") {
      instruction =
        "Research the repository and answer the user's questions. " +
        "Do NOT make any changes to any files in the repository.";
    } else {
      instruction =
        "Do the necessary changes to the codebase based on the users input.\n" +
        "Don't ask any follow up questions.";
    }
    
    // Add Claude Code system prompt when using OAuth
    if (this.useOAuth) {
      instruction = "You are Claude Code, Anthropic's official CLI for Claude. " + instruction;
    }

    const escapedPrompt = this.escapePrompt(prompt);

    return {
      command: `echo "${escapedPrompt}" | claude -p --append-system-prompt "${this.escapePrompt(instruction)}"${
        mode === "ask" ? ' --disallowedTools "Edit" "Replace" "Write"' : ""
      } --output-format stream-json --verbose --model ${
        this.model || "claude-sonnet-4-20250514"
      }`,
      errorPrefix: "Claude",
      labelName: "claude",
      labelColor: "FF6B35",
      labelDescription: "Generated by Claude AI agent",
    };
  }

  protected getDefaultTemplate(): string {
    return "vibekit-claude";
  }

  protected getEnvironmentVariables(): Record<string, string> {
    const envVars: Record<string, string> = {};
    
    if (this.useOAuth) {
      // For OAuth, pass the token as CLAUDE_CODE_OAUTH_TOKEN
      envVars.CLAUDE_CODE_OAUTH_TOKEN = this.oauthToken!;
    } else {
      // For API key authentication
      envVars.ANTHROPIC_API_KEY = this.anthropicApiKey!;
    }
    
    return envVars;
  }

  protected getApiKey(): string {
    // Return OAuth token if using OAuth, otherwise API key
    return this.useOAuth ? this.oauthToken! : this.anthropicApiKey!;
  }

  protected getAgentType(): AgentType {
    return "claude";
  }

  protected getModelConfig(): ModelConfig {
    return {
      provider: "anthropic",
      apiKey: this.useOAuth ? this.oauthToken! : this.anthropicApiKey!,
      model: this.model,
    };
  }

  // Override generateCode to support history in the instruction
  public async generateCode(
    prompt: string,
    mode?: "ask" | "code",
    branch?: string,
    history?: Conversation[],
    callbacks?: ClaudeStreamCallbacks,
    background?: boolean
  ): Promise<ClaudeResponse> {
    // Ensure token is initialized
    await this.initializeToken();
    let instruction: string;
    if (mode === "ask") {
      instruction =
        "Research the repository and answer the user's questions. " +
        "Do NOT make any changes to any files in the repository.";
    } else {
      instruction =
        "Do the necessary changes to the codebase based on the users input.\n" +
        "Don't ask any follow up questions.";
    }

    if (history && history.length > 0) {
      instruction += `\n\nConversation history: ${history
        .map((h) => `${h.role}\n ${this.escapePrompt(h.content)}`)  // Escape history content too!
        .join("\n\n")}`;
    }
    
    // Add Claude Code system prompt when using OAuth
    if (this.useOAuth) {
      instruction = "You are Claude Code, Anthropic's official CLI for Claude. " + instruction;
    }

    const escapedPrompt = this.escapePrompt(prompt);

    // Override the command config with history-aware instruction
    const originalGetCommandConfig = this.getCommandConfig.bind(this);
    this.getCommandConfig = (p: string, m?: "ask" | "code") => ({
      ...originalGetCommandConfig(p, m),
      command: `echo "${escapedPrompt}" | claude -p --append-system-prompt "${this.escapePrompt(instruction)}"${
        mode === "ask" ? ' --disallowedTools "Edit" "Replace" "Write"' : ""
      } --output-format stream-json --verbose --allowedTools "Edit,Write,MultiEdit,Read,Bash" --model ${
        this.model || "claude-sonnet-4-20250514"
      }`,
    });

    const result = await super.generateCode(
      prompt,
      mode,
      branch,
      history,
      callbacks,
      background
    );

    // Restore original method
    this.getCommandConfig = originalGetCommandConfig;

    return result as ClaudeResponse;
  }
}
